﻿#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
// 指针数组是数组 是存放指针的数组  char* arr[6];  int* arr2[5]
// 数组指针是指针  是指向数组的指针
//  int (*p) [10]; //p就是数组指针 数组有10个元素  每个元素的类型是int   p的类型是 int(*)[10]
// 把名字去掉剩下的就是类型
//int main()
//{
	//int a = 10;
	//int* p = &a;
	//*p; //得到a    我们可以使用解引用*和&取地址是抵消的   *&a == a

	//int arr[10] = { 0 };
	//int (*p)[10] = &arr;  //取出的是数组的地址

	//// 想使用p这个数组指针访问arr数组的内容
	//int i = 0;
	//for (i = 0; i < 10; i++)
	//{
	//	printf("%d ", (*p)[i]);  //*p之后得到数组  然后再使用下标访问即可
	//}   


	//上面那种很别扭 我们直接这样即可
//	int arr[10] = { 0 };
//	int *p = arr;  //取出的是数组第一个元素的地址
//
//	// 想使用p这个数组指针访问arr数组的内容
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", p[i]);  // p[i] --- *(p+i)
//	}
//	return 0;
//}

//二维数组传参的本质
//void Print(int arr[3][5], int r, int c)
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < 5; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n"); // 打印完一行换行
//	}
//}
//int main()
//{
//	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
//	Print(arr, 3, 5); // 将arr数组的内容打印出来
//	return 0;
//}
//
// 一维数组传参 数组名是首元素的地址  一维数组在传参的时候，其实传递的是首元素的地址
// 函数的参数可以写成数组，也可以写成指针
//
// 二维数组呢？ 其实二维数组的数组名也是数组首元素的地址 
// 二维数组可以理解为一维数组的数组  二维数组的每一行可以看做是一个一维数组
// 二维数组的首元素就是他的第一行  ,二维数组的数组名是他第一行的地址
// 第一行是一个一维数组 所以传这个这个一维数组的地址
//
//所以，根据数组名是数组⾸元素的地址这个规则，⼆维数组的数组名表⽰的就是第⼀⾏的地址，是⼀
//维数组的地址。根据上⾯的例⼦，第⼀⾏的⼀维数组的类型就是 int[5] ，所以第⼀⾏的地址的类
//型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址，传递的是第⼀
//⾏这个⼀维数组的地址，那么形参也是可以写成指针形式的。如下：
//void Print(int (*arr)[5], int r, int c)  //数组指针
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < 5; j++)
//		{
//			printf("%d ", *(*(arr + i)+j));  //*(arr+i)得到某一行  然后在加上j得到这行的某个元素然后再解引用
//		} // *(arr + i) == arr[i]   arr[i]是第i行的数组名  数组名又表示数组首元素的地址,arr[i]表示是arr[i][0]的地址
//		printf("\n"); // 打印完一行换行
//	}
//}
//int main()
//{
//	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
//	Print(arr, 3, 5); // 将arr数组的内容打印出来
//	return 0;
//}
//int (*arr)[5] arr是指向第一行的地址   arr+1才到第二行

// 二维数组传参的时候 形参部分既可以写成数组 也可以写成指针
// 不管是一维数组传参还是二维数组传参 形参部分都是可以写成数组，或者指针,都需要对数组名做一个深入的理解

//函数指针变量   变量有地址 数组有地址  函数其实也有地址
//int Add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	//int a = 10;
//	//int* pa = &a; //整型指针
//
//	//int arr[5] = { 0 };
//	//int (*parr)[5] = &arr;  // 数组指针
//	//printf("%p\n", &Add);  //我们发现函数也是有地址的
//	//printf("%p\n", Add);  //我们发现函数也是有地址的  
//	// &函数名 和函数名都是函数的地址 没有区别 和之前的&arr arr是不一样的
//
//	// pf是函数指针变量
//	int (*pf)(int,int) = &Add;   // int 是pf指向函数返回的类型  （）是交代清楚pf指向的函数的参数类型和个数交代
//	// int (*)(int,int)是函数指针类型  去掉名字即可
//	//int (*pf)(int x, int y) = &Add;   // 加上x y也可以
//
//	//通过函数指针调用函数  (*pf)(4,5);
//	int ret2 = (*pf)(4, 5);   //我们不写这个*也可以
//	printf("%d\n", ret2);  //9就出来了
//
//	int ret3 = (*pf)(4, 4);   //我们不写这个*也可以
//	printf("%d\n", ret3);
//
//	int ret = Add(4, 5);
//	printf("%d\n", ret);
//
//	return 0;
//}

//当我们得到函数指针变量的时候 解引用符号也可以不写的  如果写了就必须加*号
//确实打印出来了地址，所以函数是有地址的，函数名就是函数的地址，当然也可以通过& 函数名 的⽅
//式获得函数的地址。
//如果我们要将函数的地址存放起来，就得创建函数指针变量咯，函数指针变量的写法其实和数组指针
//⾮常类似。

//int main()
//{
//	( * (void (*)() ) 0 )();  //理解这段代码(void (*)())是函数指针类型
//	// (void (*)()) 0 是强制类型转换   比如(int)3.14 
//	// 把0强制性转换为函数指针类型  把0当成一个函数的地址  这个函数无参 返回类型void
//	// 然后再解引用调用这个函数
//	// 所以意思是 一次函数调用  调用0地址处放的这个函数
//	// 0地址放的这个函数的没有参数，返回类型无参
//	return 0;
//}

//int main()
//{
//	void (  *signal(  int, void(*)(int)  )  )(int);
//	// signal(  int, void(*)(int) 是一个函数  函数参数是 int 和 void（*）int  后面这个是一个函数指针类型
//	// 剩下的就是这个函数的返回类型  void(*)(int) -- 函数指针类型
//   // 这是一个函数声明  函数的名字叫:signal  signal函数的参数有2个 第一个参数的类型是int  第二个参数的类型是 一种函数指针
//   void(*)(int)  该函数指针指向的函数参数是int  返回类型是void  signal函数的返回类型也是一个函数指针，类型是void(*)(int)
//	return 0;
//}

// 函数定义  
//int Add(int x,int y)
//{
//	return x + y;
//}

//函数调用 Add(3,5);
// 函数声明  int Add（int,int）

//typedef关键字
//typedef 是⽤来类型重命名的，可以将复杂的类型，简单化。

//⽐如，你觉得 unsigned int 写起来不⽅便，如果能写成 uint 就⽅便多了，那么我们可以使⽤：
//typedef unsigned int uint;
//int main()
//{
//	unsigned int num;
//	uint num2;  //这两句是一样的
//	return 0;
//}

//typedef int* pint_t;
//int main()
//{
//	int* p;
//	pint_t p2; //这两个一样
//	return 0;
//}

//但是对于数组指针和函数指针稍微有点区别：
//⽐如我们有数组指针类型 int(*)[5], 需要重命名为 parr_t ，那可以这样写：
//typedef int(*parr_t)[5]; //新的类型名必须在*的右边

//int main()
//{
//	int arr[6] = { 0 };
//	int (*p)[6] = &arr;  //p是数组指针变量
//	parr_t p2 = &arr;//这两句是一样的
//	return 0;
//}

//函数指针类型的重命名也是⼀样的，⽐如，将 void(*)(int) 类型重命名为 pf_t, 就可以这样写：
//typedef void(*pf_t)(int);//新的类型名必须在*的右边

// int Add(int x, int y)
//{
//	return x + y;
//}

//int main()
//{
//	int (*pf)(int, int) = Add;
//	pf_t pf2= Add;  //这两句一样
//	return 0;
//}

//void (*signal(int, void(*)(int)))(int);
//简化这个代码
//typedef void ( *pf_t)(int);

//int main()
//{
//	pf_t signal(int, pf_t); //可以简化为这样
//}


// 指针数组
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	return x / y;
//}
//
//int main()
//{
//	int* arr[6];  //整型指针数组
//	char* arr2[5]; // 字符指针数组
//
//	//arr3[5];  // 每个元素都是函数的地址 - 函数指针数组
//	int (*pf1)(int, int) = Add;
//	int (*pf2)(int, int) = Sub;
//	int (*pf3)(int, int) = Mul;
//	int (*pf4)(int, int) = Div;
//
//	// 函数指针数组，来存放这些函数的地址
//	int (* pf[4])(int, int) = { Add , Sub , Mul, Div };
//
//	int i = 0;
//	for (i = 0; i < 4; i++)
//	{
//		int ret = pf[i](6, 2);
//		printf("%d\n", ret);  // 发现确实可以
//	}
//	
//	return 0;
//}

// 想实现一个计算器
// 完成：整数的加法 减法 乘法 除法
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	return x / y;
//}
//void menu()
//{
//	printf("******************************\n");
//	printf("******  1. add   2. sub    ***\n");
//	printf("******  3. mul   4. div    ***\n");
//	printf("******  0. exit           ****\n");
//	printf("******************************\n");
//}
//
//int main()
//{
//	int input = 0;
//	int x = 0;
//	int y = 0;
//	int ret = 0;
//	do
//	{
//		menu();
//		printf("请选择: ");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			printf("请输入两个操作数: ");
//			scanf("%d %d", &x, &y);
//			ret = Add(x, y);
//			printf("%d\n", ret);
//			break;		
//		case 2:
//			printf("请输入两个操作数: ");
//			scanf("%d %d", &x, &y);
//			ret = Sub(x, y);
//			printf("%d\n", ret);
//				break;
//		case 3:
//			printf("请输入两个操作数: ");
//			scanf("%d %d", &x, &y);
//			ret = Mul(x, y);
//			printf("%d\n", ret);
//			break;
//		case 4:
//			printf("请输入两个操作数: ");
//			scanf("%d %d", &x, &y);
//			ret = Div(x, y);
//			printf("%d\n", ret);
//			break;
//		case 0:
//			printf("退出计算器\n");
//			break;
//		default:
//			printf("选择错误，请重新选择\n");
//			break;
//		}
//	} while (input);
//	return 0;
//}

// 改造
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	return x / y;
//}
//void menu()
//{
//	printf("******************************\n");
//	printf("******  1. add   2. sub    ***\n");
//	printf("******  3. mul   4. div    ***\n");
//	printf("******  0. exit           ****\n");
//	printf("******************************\n");
//}
//
//int main()
//{
//	int input = 0;
//	int x = 0;
//	int y = 0;
//	int ret = 0;
//	int (*pfArr[5])(int, int) = {0,Add,Sub,Mul,Div};  //前后加个0和菜单匹配
//	do
//	{
//		menu();
//		printf("请选择: ");
//		scanf("%d", &input);
//
//		if (input >= 1 && input <= 4)
//		{
//			printf("请输入两个操作数: ");
//			scanf("%d %d", &x, &y);
//			ret = pfArr[input](x, y);
//			printf("%d\n", ret);
//		}
//		else if (input == 0)
//		{
//			printf("退出计算器\n");
//		}
//		else
//		{
//			printf("选择错误，请重新选择\n");
//		}
//	} while (input);
//	return 0;
//}

// 函数指针数组 叫做转移表

//回调函数就是⼀个通过函数指针调⽤的函数。
//如果你把函数的指针（地址）作为参数传递给另⼀个函数，当这个指针被⽤来调⽤其所指向的函数
//时，被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤，⽽是在特定的事件或条
//件发⽣时由另外的⼀⽅调⽤的，⽤于对该事件或条件进⾏响应。
//只有调⽤函数的逻辑是有差异的，我们可以把调⽤的函数的地址以参数的形式
//传递过去，使⽤函数指针接收，函数指针指向什么函数就调⽤什么函数，这⾥其实使⽤的就是回调函
//数的功能。